/*
 * Copyright (c) 2000-2003 Yale University. All rights reserved.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY DISCLAIMED. IN NO EVENT SHALL YALE UNIVERSITY OR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED, THE COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Redistribution and use of this software in source or binary forms, with or without modification, are permitted, provided that the following conditions are met:
 * 
 * 1. Any redistribution must include the above copyright notice and disclaimer and this list of conditions in any related documentation and, if feasible, in the redistributed software.
 * 
 * 2. Any redistribution must include the acknowledgment, "This product includes software developed by Yale University," in any related documentation and, if feasible, in the redistributed software.
 * 
 * 3. The names "Yale" and "Yale University" must not be used to endorse or promote products derived from this software.
 */

package edu.yale.its.tp.cas.client.taglib;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import edu.yale.its.tp.cas.client.*;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;

/**
 * <p>
 * Authentication tag for use with the Yale Central Authentication Service.
 * </p>
 * 
 * <p>
 * Typical usage involves placing the tag at the top of the page. The tag checks to determine if the attribute referenced by id/scope exists; if it does, the tag has no runtime effect. If the attribute does not exist, however, a CAS authentication is necessary: if no ticket is present, we redirect
 * to CAS, and if a ticket is present, we validate it. Upon successful CAS authentication (either by a pre-existing attribute or through CAS directly), we store the NetID in the attribute referenced by id/scope.
 * </p>
 * 
 * @author Shawn Bayern
 * @author Drew Mazurek
 */
public class AuthTag extends TagSupport
{

	// *********************************************************************
	// Internal state

	private String var; // tag attribute

	private int scope; // tag attribute

	private String casLogin, casValidate, service; // from children

	private List acceptedProxies; // from children

	private HttpServletRequest request;

	private HttpServletResponse response;

	// *********************************************************************
	// Tag logic

	public int doStartTag() throws JspException
	{
		// retrieve and save the request and response objects
		request = (HttpServletRequest) pageContext.getRequest();
		response = (HttpServletResponse) pageContext.getResponse();

		// reset invocation-specific state
		casLogin = null;
		casValidate = null;
		try
		{
			service = Util.getService(request, (String) pageContext.getServletContext().getInitParameter("edu.yale.its.tp.cas.serverName"));
		}
		catch (ServletException ex)
		{
			throw new JspException(ex);
		}
		acceptedProxies = new ArrayList();
		return EVAL_BODY_INCLUDE;
	}

	public int doEndTag() throws JspTagException
	{
		try
		{
			// if our attribute's already present, don't do anything
			if (pageContext.getAttribute(var, scope) != null)
				return EVAL_PAGE;

			// otherwise, we need to authenticate via CAS
			String ticket = request.getParameter("ticket");

			// no ticket? redirect...
			if (ticket == null || ticket.equals(""))
			{
				if (casLogin == null)
					throw new JspTagException("for pages that expect to be called without 'ticket' parameter, " + "cas:auth must have a cas:loginUrl subtag");
				response.sendRedirect(casLogin + "?service=" + service);
				return SKIP_PAGE;
			}

			// Yay, ticket! Validate it.
			String netid = getAuthenticatedNetid(ticket);
			if (netid == null)
				throw new JspTagException("Unexpected CAS authentication error");

			// Store the authenticate user in the id/scope attribute
			pageContext.setAttribute(var, netid, scope);

			return EVAL_PAGE;

		}
		catch (IOException ex)
		{
			throw new JspTagException(ex.getMessage());
		}
		catch (SAXException ex)
		{
			throw new JspTagException(ex.getMessage());
		}
		catch (ParserConfigurationException ex)
		{
			throw new JspTagException(ex.getMessage());
		}
	}

	// *********************************************************************
	// Attribute accessors

	public void setVar(String var)
	{
		this.var = var;
	}

	public void setScope(String scope)
	{
		if (scope.equals("page"))
			this.scope = PageContext.PAGE_SCOPE;
		else if (scope.equals("request"))
			this.scope = PageContext.REQUEST_SCOPE;
		else if (scope.equals("session"))
			this.scope = PageContext.SESSION_SCOPE;
		else if (scope.equals("application"))
			this.scope = PageContext.APPLICATION_SCOPE;
		else
			throw new IllegalArgumentException("invalid scope");
	}

	// *********************************************************************
	// Accessors for child tags

	public void setCasLogin(String url)
	{
		casLogin = url;
	}

	public void setCasValidate(String url)
	{
		casValidate = url;
	}

	public void addAuthorizedProxy(String proxyId)
	{
		acceptedProxies.add(proxyId);
	}

	public void setService(String service)
	{
		this.service = service;
	}

	// *********************************************************************
	// Constructor and lifecycle management

	public AuthTag()
	{
		super();
		init();
	}

	// Releases any resources we may have (or inherit)
	public void release()
	{
		super.release();
		init();
	}

	// clears any internal state we might have
	private void init()
	{
		var = null;
		scope = PageContext.PAGE_SCOPE;
		casLogin = null;
		casValidate = null;
		acceptedProxies = null;
	}

	// *********************************************************************
	// Utility methods

	private String getAuthenticatedNetid(String ticket) throws ParserConfigurationException, SAXException, IOException, JspTagException
	{
		ProxyTicketValidator pv = new ProxyTicketValidator();
		pv.setCasValidateUrl(casValidate);
		pv.setServiceTicket(ticket);
		pv.setService(service);
		pv.validate();
		if (!pv.isAuthenticationSuccesful())
			throw new JspTagException("CAS authentication error: " + pv.getErrorCode());
		if (pv.getProxyList().size() != 0)
		{
			// ticket was proxied
			if (acceptedProxies.size() == 0)
				throw new JspTagException("this page does not accept proxied tickets");
			else if (!acceptedProxies.contains(pv.getProxyList().get(0)))
				throw new JspTagException("unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'");
		}
		return pv.getUser();
	}
}
